/**
 * Copyright (c) 2022 qigaohua168@163.com.
 * All rights reserved.
 *
 * Created on 2022/12/29 11:25.
 */

#ifndef LOG_H_MP8AFCTH
#define LOG_H_MP8AFCTH
#include <atomic>
#include <string>
#include <algorithm>
#include <memory>
#include <vector>
#include <list>
#include <unordered_map>
#include <functional>
#include <mutex>
#include <thread>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <inttypes.h>


// %L          log level
// %Y-%m-%d    year-mon-day    ==   %D
// %H:%M:%S    hour:min:sec    ==   %T
// %n          logger name
// %f          file name
// %l          file line
// %C          %f:%l
// %t          thread id
// %v          user log message
// %^          console color start pos
// %$          console color end pos
// %X          %^[%L]%$ [%Y-%m-%d %H:%M:%S] [%n] [%f:%l] %v             == default
// %a          %^[%L]%$ [%Y-%m-%d %H:%M:%S] [%n] [%t] [%f:%l] %v        == show all
//
//
//              %D       %T            %C
// %^[%L]%$ [%Y-%m-%d %H:%M:%S] [%n] [%f:%l] %v         ==  %X   ==  default
// %^[%L]%$ [%Y-%m-%d %H:%M:%S] [%n] [%t] [%f:%l] %v    ==  %a   ==  all
//
//
// [D] [2022-12-24 12:00:00.456] [test] [main.c:88] hello world!
// [I] [2022-12-24 12:00:00.456] [test] [15460384460594182018] [main.c:88] hello world!
//

namespace qlibs {

namespace qlog {

using log_clock     = std::chrono::system_clock;
using level_t       = std::atomic<int>;
// using log_buffer_t = std::array<char, 512>;
using log_buffer_t  = std::string;


#define LOG_TRACE    0
#define LOG_DEBUG    1
#define LOG_INFO     2
#define LOG_WARN     3
#define LOG_ERROR    4
#define LOG_ASSERT   5
#define LOG_OFF      6

enum log_level {
    trace = LOG_TRACE,
    debug = LOG_DEBUG,
    info  = LOG_INFO,
    warn  = LOG_WARN,
    error = LOG_ERROR,
    assert = LOG_ASSERT,
    off    = LOG_OFF,
    num_levles
};
static const char* level_names[] = {"T", "D", "I", "W", "E", "A"};

inline const char* level_num_to_string(log_level l) noexcept {
    return level_names[l];
}

class LogEX : public std::exception {
public:
    explicit LogEX(std::string msg)
        : _msg(std::move(msg))
    {}
    LogEX(const std::string& msg, int _errno) {
        _msg = std::system_error(std::error_code(_errno, std::generic_category()), msg).what();
    }
    const char *what() const noexcept override;

private:
    std::string _msg;
};

[[noreturn]] inline void throw_log_ex(const std::string&msg, int _errno) {
    throw(LogEX(msg, _errno));
}
[[noreturn]] inline void throw_log_ex(const std::string&msg) {
    throw(LogEX(std::move(msg)));
}


class FileHelper {
public:
    struct file_event_handlers {
        std::function<void(const std::string& filename)> before_open;
        std::function<void(const std::string& filename, std::FILE *fp)> after_open;
        std::function<void(const std::string& filename, std::FILE *fp)> before_close;
        std::function<void(const std::string& filename)> after_close;
        file_event_handlers ()
            : before_open { nullptr }
            , after_open { nullptr }
            , before_close { nullptr }
            , after_close { nullptr }
        {}
    };

    void open(const std::string& filename);
    void reopen();
    void close();
    void flush();
    void write(const char *msg, size_t msg_len) {
        if (msg_len == ::fwrite(msg, 1, msg_len, _fp))
            return;
        throw_log_ex("Failed write to file " + _filename, errno);
    }
    ssize_t size() const {
        struct stat st;

        if (nullptr == _fp) {
            throw_log_ex("Failed to get file size -- file not open.");
        }

        if (::fstat(::fileno(_fp), &st) == 0) {
            return st.st_size;
        }
        throw_log_ex("Failed to call fstat()", errno);
    }
    const std::string& filename() const { return _filename; }

public:
    FileHelper() =default;
    explicit FileHelper(const file_event_handlers& event_handlers)
        : _event_handlers(event_handlers)
    {}
    ~FileHelper() { close(); }

    FileHelper(const FileHelper&) =delete;
    FileHelper& operator=(const FileHelper&) =delete;

private:
    const int   _open_tries { 5 };
    std::string _filename;
    std::FILE   *_fp { nullptr };
    file_event_handlers _event_handlers;

};

struct null_mutex
{
    void lock() const {  }
    void unlock() const {  }
    bool try_lock() const { return true; }
};


struct console_mutex {
    using mutex_t = std::mutex;

    static mutex_t& mutex() {
        static mutex_t _mutex;
        return _mutex;
    }
};

struct console_nullmutex {
    using mutex_t = null_mutex;

    static mutex_t& mutex() {
        static mutex_t _mutex;
        return _mutex;
    }
};



struct source_loc
{
    source_loc () =default;
    source_loc (const char *filename, const char *funcname, int line)
        : _filename(filename)
        , _funcname(funcname)
        , _line(line)
    {}

    bool empty() const { return 0 == _line; }

    const char *_filename { nullptr };
    const char *_funcname { nullptr };
    int         _line { 0 };

};


struct log_msg
{
    log_msg() =default;
    log_msg(log_clock::time_point time, source_loc loc, const char *logger, log_level level,
            const char *text)
        : _time(time)
        , _logger_name(logger)
        , _level(level)
        , _thread_id(static_cast<uint64_t>(std::hash<std::thread::id>()(std::this_thread::get_id())))
        , _source(loc)
        , _text(text)
    {
        // The above method gets the hash value of the thread id,
        // and the next method gets the thread id.
        //
        // std::thread::id id = std::this_thread::get_id();
        // _thread_id = *((uint64_t*)&id);
    }
    log_msg(source_loc loc, const char *logger, log_level level, const char *text)
        : log_msg(log_clock::now(), loc, logger, level, text)
    {}
    log_msg(const char *logger, log_level level, const char *text)
        : log_msg(log_clock::now(), source_loc{}, logger, level, text)
    {}
    log_msg(const log_msg&) =default;
    log_msg& operator=(const log_msg&) =default;

    log_clock::time_point _time;
    const char     *_logger_name { nullptr };
    log_level       _level { log_level::info };
    uint64_t        _thread_id { 0 };

    mutable size_t  _color_start_pos { 0 };
    mutable size_t  _color_end_pos   { 0 };

    source_loc      _source;
    const char     *_text { nullptr };
    // size_t          _text_len { 0 };
};


inline bool is_color_terminal() noexcept
{
    static const bool result = []() {
        const char *env_colorterm_p = std::getenv("COLORTERM");
        if (nullptr != env_colorterm_p) {
            return true;
        }

        static constexpr std::array<const char *, 16> terms = {
            {"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm", "alacritty", "vt102"}
        };

        const char *env_term_p = std::getenv("TERM");
        if (nullptr == env_term_p) {
            return false;
        }

        return std::any_of(terms.begin(), terms.end(), [&](const char *term){
            return !!strstr(env_term_p, term);
        });
    } ();

    return result;
}


namespace formatter {

class Formatter {
public:
    virtual ~Formatter();
    virtual void format(const log_msg& msg, log_buffer_t &buff) =0;
    virtual std::unique_ptr<Formatter> clone() const =0;
private:
};


class FlagFormatter {
public:
    FlagFormatter(char sc = ' ') : _split_char(sc)
    {  }
    virtual ~FlagFormatter();
    virtual void format(const log_msg& , const std::tm&, log_buffer_t&) =0;
protected:
    char    _split_char = ' ';
};


class UserFormatter : public FlagFormatter {
public:
    explicit UserFormatter(const std::string& show)
        : _show(std::move(show))
    {}
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override;

private:
    std::string _show;
};


class L_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        const char *level_name = level_num_to_string(msg._level);
        buff.append(level_name);
        // buff.append(_split_char);
    }
};

// thread id
class t_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        buff.append(std::to_string(msg._thread_id));
    }
};


class Y_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        buff.append(std::to_string(tm_.tm_year + 1900));
    }
};

class m_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char buf[16]; snprintf(buf, sizeof(buf), "%02d", tm_.tm_mon + 1);
        buff.append(buf);
    }
};

class d_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char buf[3]; snprintf(buf, sizeof(buf), "%02d", tm_.tm_mday);
        buff.append(buf);
    }
};


class D_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char buf[64]; snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
                tm_.tm_year + 1900, tm_.tm_mon + 1, tm_.tm_mday);
        buff.append(buf);
    }
};


class H_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char buf[3]; snprintf(buf, sizeof(buf), "%02d", tm_.tm_hour);
        buff.append(buf);
    }
};

class M_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char buf[3]; snprintf(buf, sizeof(buf), "%02d", tm_.tm_min);
        buff.append(buf);
    }
};

class S_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char buf[3]; snprintf(buf, sizeof(buf), "%02d", tm_.tm_sec);
        buff.append(buf);
    }
};

class T_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char buf[9]; snprintf(buf, sizeof(buf), "%02d:%02d:%02d",
                tm_.tm_hour, tm_.tm_min, tm_.tm_sec);
        buff.append(buf);
    }
};

class n_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        buff.append(msg._logger_name);
    }
};

class f_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        if (!msg._source.empty())
            buff.append(msg._source._filename);
    }
};

// line info
class l_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        if (!msg._source.empty())
            buff.append(std::to_string(msg._source._line));
    }
};

// code info
class C_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        if (!msg._source.empty()) {
            char buf[128];
            snprintf(buf, sizeof(buf), "%s:%d", msg._source._filename,
                    msg._source._line);
            buff.append(buf);
        }
    }
};

class V_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        if (msg._text) buff.append(msg._text);
    }
};

class EolFormatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm&, log_buffer_t &buff) override {
        buff.append(_eol);
    }
private:
    const std::string   _eol {"\n"};
};

class X_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char tmp[512];

        if (!msg._source.empty()) {
            snprintf(tmp, sizeof(tmp), "[%s] [%04d-%02d-%02d %02d:%02d:%02d] [%s] [%s:%d] ",
                    level_num_to_string(msg._level),
                    tm_.tm_year + 1900, tm_.tm_mon + 1, tm_.tm_mday,
                    tm_.tm_hour, tm_.tm_min, tm_.tm_sec,
                    msg._logger_name,
                    msg._source._filename, msg._source._line);
        }
        else {
            snprintf(tmp, sizeof(tmp), "[%s] [%04d-%02d-%02d %02d:%02d:%02d] [%s] ",
                    level_num_to_string(msg._level),
                    tm_.tm_year + 1900, tm_.tm_mon + 1, tm_.tm_mday,
                    tm_.tm_hour, tm_.tm_min, tm_.tm_sec,
                    msg._logger_name);
        }
        buff.append(tmp);
        if (msg._text) buff.append(msg._text);

        msg._color_start_pos = 0;
        msg._color_end_pos   = 3;
    }
};

class a_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        char tmp[512];

        if (!msg._source.empty()) {
            snprintf(tmp, sizeof(tmp), "[%s] [%04d-%02d-%02d %02d:%02d:%02d] [%s] [%" PRIu64"] [%s:%d] ",
                    level_num_to_string(msg._level),
                    tm_.tm_year + 1900, tm_.tm_mon + 1, tm_.tm_mday,
                    tm_.tm_hour, tm_.tm_min, tm_.tm_sec,
                    msg._logger_name,
                    msg._thread_id,
                    msg._source._filename, msg._source._line);
        }
        else {
            snprintf(tmp, sizeof(tmp), "[%s] [%04d-%02d-%02d %02d:%02d:%02d] [%s] [%" PRIu64"] ",
                    level_num_to_string(msg._level),
                    tm_.tm_year + 1900, tm_.tm_mon + 1, tm_.tm_mday,
                    tm_.tm_hour, tm_.tm_min, tm_.tm_sec,
                    msg._logger_name,
                    msg._thread_id);
        }
        buff.append(tmp);
        if (msg._text) buff.append(msg._text);

        msg._color_start_pos = 0;
        msg._color_end_pos   = 3;
    }
};

// ^
class color_start_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        msg._color_start_pos = buff.length();
    }
};

// $
class color_end_Formatter : public FlagFormatter {
public:
    void format(const log_msg& msg, const std::tm& tm_, log_buffer_t &buff) override {
        msg._color_end_pos = buff.length();
    }
};

class PatternFormatter final: public Formatter {
public:
    explicit PatternFormatter(std::string pattern)
        : _pattern(std::move(pattern)) {
        _compile_pattern(_pattern);
    }
    ~PatternFormatter() =default;

    void format(const log_msg& msg, log_buffer_t &buff) override {
        auto secs = std::chrono::duration_cast<std::chrono::seconds>(msg._time.time_since_epoch());

        if (secs != _last_log_secs) {
            time_t ts = log_clock::to_time_t(msg._time);
            localtime_r(&ts, &_tm_cache);
            _last_log_secs = secs;
        }

        for (auto& formatter : _formatters) {
            formatter->format(msg, _tm_cache, buff);
        }
    }
    std::unique_ptr<Formatter> clone() const override { return nullptr; }

    void set_pattern(const std::string& pattern) {
        _formatters.clear();
        _compile_pattern(pattern);
    }

private:
    std::string             _pattern;
    std::vector<std::unique_ptr<FlagFormatter>> _formatters;

    std::chrono::seconds    _last_log_secs {0};
    std::tm                 _tm_cache;

    void _compile_pattern(const std::string& pattern) {
        std::string user_flag;

        for (auto it = pattern.begin(); it != pattern.end(); ++it) {
            if (*it != '%') {
                user_flag.push_back(*it);
                continue;
            }
            if (std::next(it) == pattern.end()) break;

            if (*std::next(it) == '%') {
                user_flag.push_back(*it++);
                continue;
            }

        #if __cplusplus >= 201402L
            _formatters.push_back(std::make_unique<UserFormatter>(std::move(user_flag)));
        #else
            _formatters.push_back(std::unique_ptr<UserFormatter>(new UserFormatter(std::move(user_flag))));
        #endif

            // user_flag.clear();

            switch (*(++it)) {
        #if __cplusplus >= 201402L
                case 'L': { _formatters.push_back(std::make_unique<L_Formatter>()); } break;
                case 'n': { _formatters.push_back(std::make_unique<n_Formatter>()); } break;
                case 'Y': { _formatters.push_back(std::make_unique<Y_Formatter>()); } break;
                case 'm': { _formatters.push_back(std::make_unique<m_Formatter>()); } break;
                case 'd': { _formatters.push_back(std::make_unique<d_Formatter>()); } break;
                case 'D': { _formatters.push_back(std::make_unique<D_Formatter>()); } break;
                case 'H': { _formatters.push_back(std::make_unique<H_Formatter>()); } break;
                case 'M': { _formatters.push_back(std::make_unique<M_Formatter>()); } break;
                case 'S': { _formatters.push_back(std::make_unique<S_Formatter>()); } break;
                case 'T': { _formatters.push_back(std::make_unique<T_Formatter>()); } break;
                case 'f': { _formatters.push_back(std::make_unique<f_Formatter>()); } break;
                case 'l': { _formatters.push_back(std::make_unique<l_Formatter>()); } break;
                case 't': { _formatters.push_back(std::make_unique<t_Formatter>()); } break;
                case 'c':
                case 'C': { _formatters.push_back(std::make_unique<C_Formatter>()); } break;
                case 'X': { _formatters.push_back(std::make_unique<X_Formatter>()); } break;
                case 'a': { _formatters.push_back(std::make_unique<a_Formatter>()); } break;
                case 'v':
                case 'V': { _formatters.push_back(std::make_unique<V_Formatter>()); } break;
                case '^': { _formatters.push_back(std::make_unique<color_start_Formatter>()); } break;
                case '$': { _formatters.push_back(std::make_unique<color_end_Formatter>()); } break;
        #else
                case 'L': { _formatters.push_back(std::unique_ptr<L_Formatter>(new L_Formatter)); } break;
                case 'n': { _formatters.push_back(std::unique_ptr<n_Formatter>(new n_Formatter)); } break;
                case 'Y': { _formatters.push_back(std::unique_ptr<Y_Formatter>(new Y_Formatter)); } break;
                case 'm': { _formatters.push_back(std::unique_ptr<m_Formatter>(new m_Formatter)); } break;
                case 'd': { _formatters.push_back(std::unique_ptr<d_Formatter>(new d_Formatter)); } break;
                case 'D': { _formatters.push_back(std::unique_ptr<D_Formatter>(new D_Formatter)); } break;
                case 'H': { _formatters.push_back(std::unique_ptr<H_Formatter>(new H_Formatter)); } break;
                case 'M': { _formatters.push_back(std::unique_ptr<M_Formatter>(new M_Formatter)); } break;
                case 'S': { _formatters.push_back(std::unique_ptr<S_Formatter>(new S_Formatter)); } break;
                case 'T': { _formatters.push_back(std::unique_ptr<T_Formatter>(new T_Formatter)); } break;
                case 'f': { _formatters.push_back(std::unique_ptr<f_Formatter>(new f_Formatter)); } break;
                case 'l': { _formatters.push_back(std::unique_ptr<l_Formatter>(new l_Formatter)); } break;
                case 't': { _formatters.push_back(std::unique_ptr<t_Formatter>(new t_Formatter)); } break;
                case 'c':
                case 'C': { _formatters.push_back(std::unique_ptr<C_Formatter>(new C_Formatter)); } break;
                case 'X': { _formatters.push_back(std::unique_ptr<X_Formatter>(new X_Formatter)); } break;
                case 'a': { _formatters.push_back(std::unique_ptr<a_Formatter>(new a_Formatter)); } break;
                case 'v':
                case 'V': { _formatters.push_back(std::unique_ptr<V_Formatter>(new V_Formatter)); } break;
                case '^': { _formatters.push_back(std::unique_ptr<color_start_Formatter>(new color_start_Formatter)); } break;
                case '$': { _formatters.push_back(std::unique_ptr<color_end_Formatter>(new color_end_Formatter)); } break;
        #endif
                default: {  }
            }
        }

        if (!user_flag.empty()) {
        #if __cplusplus >= 201402L
            _formatters.push_back(std::make_unique<UserFormatter>(std::move(user_flag)));
        #else
            _formatters.push_back(std::unique_ptr<UserFormatter>(new UserFormatter(std::move(user_flag))));
        #endif
        }

        #if __cplusplus >= 201402L
        _formatters.push_back(std::make_unique<EolFormatter>());
        #else
        _formatters.push_back(std::unique_ptr<EolFormatter>(new EolFormatter));
        #endif
    }
};

} // namespace formatter


namespace sink {
class Sink {
public:
    virtual ~Sink() = default;

    virtual void log(const log_msg& msg) =0;
    virtual void flush() =0;

    virtual void set_pattern(const std::string& ) =0;
    virtual void set_formatter(std::unique_ptr<formatter::Formatter> )=0;

    void set_level(log_level level);
    log_level level() const;

    bool should_log(log_level msg_level) const;

protected:
    level_t _level { log_level::trace };
};


using sink_ptr = std::shared_ptr<Sink>;
using sinks_init_list = std::initializer_list<sink_ptr>;


template <typename Mutex>
class Console_Sink : public Sink {
public:
    using mutex_t = typename Mutex::mutex_t;

    explicit Console_Sink(std::FILE* target, const std::string& pattern = "%X")
        : _target(target)
        , _mutex(Mutex::mutex())
    #if __cplusplus >= 201402L
        , _formatter(std::make_unique<formatter::PatternFormatter>(pattern))
    #else
        , _formatter(std::unique_ptr<formatter::PatternFormatter>(new formatter::PatternFormatter(pattern)))
    #endif
    {
        _should_show_color = is_color_terminal();

        _colors[log_level::trace] = white;
        _colors[log_level::debug] = cyan;
        _colors[log_level::info]  = green;
        _colors[log_level::warn]  = yellow;
        _colors[log_level::error] = red;
        _colors[log_level::assert]= bold_on_red;
    }
    ~Console_Sink() =default;

    Console_Sink(const Console_Sink&) =delete;
    Console_Sink(Console_Sink&&) =delete;
    Console_Sink& operator=(const Console_Sink&) =delete;
    Console_Sink& operator=(Console_Sink&&) =delete;

    const std::string reset     = "\033[m";

    const std::string black     = "\033[30m";
    const std::string red       = "\033[31m";
    const std::string green     = "\033[32m";
    const std::string yellow    = "\033[33m";
    const std::string blue      = "\033[34m";
    const std::string magenta   = "\033[35m";
    const std::string cyan      = "\033[36m";
    const std::string white     = "\033[37m";

    const std::string bold_on_red = "\033[1m\033[41m";


    void log(const log_msg& msg) override {
        std::lock_guard<mutex_t> lock(_mutex);
        log_buffer_t buff;

        _formatter->format(msg, buff);
        if (_should_show_color && msg._color_end_pos > msg._color_start_pos) {
            fwrite(buff.c_str(), sizeof(char), msg._color_start_pos, _target);

            fwrite(_colors[msg._level].c_str(), sizeof(char), _colors[msg._level].size(),
                    _target);
            fwrite(&buff.c_str()[msg._color_start_pos], sizeof(char),
                    msg._color_end_pos - msg._color_start_pos, _target);
            fwrite(reset.c_str(), sizeof(char), reset.size(), _target);

            fwrite(&buff.c_str()[msg._color_end_pos], sizeof(char),
                    buff.size() - msg._color_end_pos, _target);
        }
        else {
            fwrite(buff.c_str(), sizeof(char), buff.size(), _target);
        }
    }

    void flush() override {
        std::lock_guard<mutex_t> lock(_mutex);
        ::fflush(_target);
    }

    virtual void set_pattern(const std::string& pattern) override {
    #if __cplusplus >= 201402L
        std::unique_ptr<formatter::PatternFormatter> formatter_
            = std::make_unique<formatter::PatternFormatter>(pattern);
    #else
        std::unique_ptr<formatter::PatternFormatter> formatter_
            = std::unique_ptr<formatter::PatternFormatter>(new formatter::PatternFormatter(pattern));
    #endif
        set_formatter(std::move(formatter_));
    }

    virtual void set_formatter(std::unique_ptr<formatter::Formatter> formatter) override {
        _formatter.reset();
        _formatter = std::move(formatter);
    }

private:
    std::FILE  *_target { nullptr };
    bool        _should_show_color { true };
    mutex_t&    _mutex;

    std::array<std::string, log_level::num_levles> _colors;
    std::unique_ptr<formatter::Formatter> _formatter;
};


template <typename Mutex>
class ConsoleStdout_Sink : public Console_Sink<Mutex> {
public:
    explicit ConsoleStdout_Sink(const std::string& pattern = "%X")
        : Console_Sink<Mutex>(stdout, pattern)
    {}
};

template <typename Mutex>
class ConsoleStderr_Sink : public Console_Sink<Mutex> {
public:
    explicit ConsoleStderr_Sink(const std::string& pattern = "%X")
        : Console_Sink<Mutex>(stderr, pattern)
    {}
};


template <typename Mutex>
class BaseSink : public Sink {
public:
    BaseSink () {  }
    explicit BaseSink (std::unique_ptr<formatter::Formatter> formatter)
        : _formatter(std::move(formatter))
    {}
    virtual ~BaseSink() = default;

    BaseSink(const BaseSink&) =delete;
    BaseSink(BaseSink&&) =delete;
    BaseSink& operator=(const BaseSink&) =delete;
    BaseSink& operator=(BaseSink&&) =delete;

    void log(const log_msg& msg) final {
        std::lock_guard<Mutex> lock(_mutex);
        _sink_it(msg);
    }

    void flush() final {
        std::lock_guard<Mutex> lock(_mutex);
        _flush();
    }

    void set_pattern(const std::string& pattern) final {
    #if __cplusplus >= 201402L
        std::unique_ptr<formatter::PatternFormatter> formatter_
            = std::make_unique<formatter::PatternFormatter>(pattern);
    #else
        std::unique_ptr<formatter::PatternFormatter> formatter_
            = std::unique_ptr<formatter::PatternFormatter>(new formatter::PatternFormatter(pattern));
    #endif
        set_formatter(std::move(formatter_));
    }
    void set_formatter(std::unique_ptr<formatter::Formatter> formatter) final {
        _formatter.reset();
        _formatter = std::move(formatter);
    }

protected:
    std::unique_ptr<formatter::Formatter> _formatter;
    Mutex _mutex;

    virtual void _sink_it(const log_msg& msg) =0;
    virtual void _flush()   =0;
};


template <typename Mutex>
class BasicFile_Sink : public BaseSink<Mutex> {
public:
    explicit BasicFile_Sink(const std::string& filename, const std::string& pattern = "%X",
            const FileHelper::file_event_handlers &event_handlers = {})
        // , BaseSink<Mutex>(std::make_unique<formatter::PatternFormatter>("[%L] [%Y-%m-%d %T] [%n] %v"))
    #if __cplusplus >= 201402L
        : BaseSink<Mutex>(std::make_unique<formatter::PatternFormatter>(pattern))
    #else
        : BaseSink<Mutex>(std::unique_ptr<formatter::PatternFormatter>(new formatter::PatternFormatter(pattern)))
    #endif
        , _file_helper(event_handlers)
    {
        _file_helper.open(filename);
    }
    BasicFile_Sink(const std::string& filename, size_t max_file_size, int max_files)
        : BasicFile_Sink(filename)
    {
        // _max_file_size = max_file_size;
        // _max_files     = max_files;
        set_max_file_size(max_file_size);
        set_max_files(max_files);
    }
    virtual ~BasicFile_Sink() =default;

    const std::string& filename() const { return _file_helper.filename(); }

    void set_max_files(int file_cnt) {
        if (file_cnt <= 0)
            return;
        _max_files = file_cnt;
    }
    int max_files() const {
        return _max_files;
    }

    void set_max_file_size(size_t file_size) {
        if (file_size < 1024)
            return;
        _max_file_size = file_size;
    }
    size_t max_file_size() const {
        return _max_file_size;
    }

protected:
    void  _file_rename(const std::string& new_filename) {
        if (0 != ::rename(filename().c_str(), new_filename.c_str()))
            throw_log_ex("Failed to call ::rename "
                    + filename() + " -> " + new_filename, errno);
    }
    void  _file_remove(const std::string& old_filename) {
        if (0 != ::remove(old_filename.c_str()))
            throw_log_ex("Failed to call ::remove " + old_filename, errno);
    }

    void _check_file_cnt() {
        if ((int)_files.size() > _max_files) {
            const std::string& old_file = _files.front();
            _file_remove(old_file.c_str());
            _files.pop_front();
        }
    }

    void _check_filesize() {
        size_t file_size = _file_helper.size();

        if (file_size >= _max_file_size) {
            _file_helper.flush();
            _file_helper.close();

            std::string new_filename = filename() + "."
                // + std::to_string(log_clock::now().time_since_epoch().count()); // ns
                + std::to_string(log_clock::to_time_t(log_clock::now()));

            _file_rename(new_filename);
            _files.push_back(std::move(new_filename));
            _check_file_cnt();

            _file_helper.reopen();
        }
    }

    void _sink_it(const log_msg& msg) override {
        static int writelog_cnt = 0;
        log_buffer_t buff;

        BaseSink<Mutex>::_formatter->format(msg, buff);

        if (++writelog_cnt >= _check_filesize_cnt_interval) {
            _check_filesize();
            writelog_cnt = 0;
        }

        _file_helper.write(buff.c_str(), buff.length());
    }

    void _flush() override { _file_helper.flush(); }

    size_t      _max_file_size { 1 << 22 };  // default 4M Bytes
    int         _max_files { 3 };            // default 3 backup logs

    std::list<std::string> _files;
    const int   _check_filesize_cnt_interval { 10 };

    FileHelper  _file_helper;
};


// using console_sink_mt        = Console_Sink<console_mutex>;
// using console_sink_st        = Console_Sink<console_nullmutex>;
using console_stdout_sink_mt = ConsoleStdout_Sink<console_mutex>;
using console_stdout_sink_st = ConsoleStdout_Sink<console_nullmutex>;
using console_stderr_sink_mt = ConsoleStderr_Sink<console_mutex>;
using console_stderr_sink_st = ConsoleStderr_Sink<console_nullmutex>;
using basic_file_sink_mt     = BasicFile_Sink<std::mutex>;
using basic_file_sink_st     = BasicFile_Sink<null_mutex>;

} // namespace sink


class Logger final
{
public:
    using ptr_t = std::shared_ptr<Logger>;
    explicit Logger (std::string name) : _name(std::move(name)), _sinks() {  }

    template<typename It>
    Logger (std::string name, It begin, It end)
        : _name(std::move(name))
        , _sinks(begin, end)
    {}

    Logger (std::string name, sink::sinks_init_list sinks)
        : Logger(std::move(name), sinks.begin(), sinks.end())
    {}

    Logger (std::string name, sink::sink_ptr sink)
        : Logger(std::move(name), {std::move(sink)})
    {}


    ~Logger () = default;

    void set_pattern(const std::string& pattern) {
        for (auto& sink : _sinks) {
            sink->set_pattern(pattern);
        }
    }

    void set_level(log_level level) {
        _level.store(level, std::memory_order_relaxed);
    }
    bool should_log(log_level level) const {
        return level >= _level.load(std::memory_order_relaxed);
    }

    int log_level() const {
        return _level.load(std::memory_order_relaxed);
    }

    void set_flush_level(enum log_level level) {
        _flush_level.store(level, std::memory_order_relaxed);
    }
    bool should_flush(enum log_level level) const {
        return level >= _flush_level.load(std::memory_order_relaxed);
    }

    void flush() {
        for (auto& sink : _sinks) {
            sink->flush();
        }
    }

    void add_sink(const std::shared_ptr<sink::Sink>& sink) {
        _sinks.push_back(std::move(sink));
    }

    const std::string& name() const {
        return _name;
    }

private:
    // void _log(source_loc loc, log_level level, const char *fmt, ...);
    void _log(source_loc loc, enum log_level level, const char *fmt, va_list args);

public:
    void log(source_loc loc, enum log_level level, const char *fmt, ...) {
        va_list args;

        va_start(args, fmt);
        _log(loc, level, fmt, args);
        va_end(args);
    }

    void debug(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        _log(source_loc{}, log_level::debug, fmt, args);
        va_end(args);
    }

    void info(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        _log(source_loc{}, log_level::info, fmt, args);
        va_end(args);
    }

    void warn(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        _log(source_loc{}, log_level::warn, fmt, args);
        va_end(args);
    }

    void error(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        _log(source_loc{}, log_level::error, fmt, args);
        va_end(args);
    }

    void alert(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        _log(source_loc{}, log_level::assert, fmt, args);
        va_end(args);
    }


private:
    std::string _name;
    std::vector<sink::sink_ptr> _sinks;

    level_t _level { log_level::info };
    level_t _flush_level { log_level::off };

};

////////////////////////////////////////////////////////


class Qlog {
private:
     Qlog()
     {
         auto sink = std::make_shared<sink::console_stdout_sink_mt>();
         sink->set_level(log_level::trace);
         _default_logger = std::make_shared<Logger>(_default_logger_name, std::move(sink));
         _default_logger->set_level(log_level::trace);
     }
     ~Qlog() =default;
public:
    Qlog(const Qlog&) =delete;
    Qlog(Qlog&&) =delete;
    Qlog& operator= (const Qlog&) =delete;
    Qlog& operator= (Qlog&&) =delete;

    static Qlog& instance() {
        static Qlog qlog;
        return qlog;
    }

    Logger::ptr_t& default_logger() {
        return _default_logger;
    }

    Logger* default_logger_raw() {
        return _default_logger.get();
    }

    void register_logger(Logger::ptr_t logger) {
        std::lock_guard<std::mutex> lock(_loggers_map_mutex);
        if (_loggers.end() !=  _loggers.find(logger->name())) {
            return;
        }
        _loggers.emplace(logger->name(), std::move(logger));
    }

    void unregister_logger(const std::string& name) {
        std::lock_guard<std::mutex> lock(_loggers_map_mutex);
        _loggers.erase(name);

        if (_default_logger && _default_logger->name() == name) {
            _default_logger.reset();
        }
    }

    Logger::ptr_t get_logger(const std::string& name) {
        std::lock_guard<std::mutex> lock(_loggers_map_mutex);
        auto logger = _loggers.find(name);
        return logger != _loggers.end() ? logger->second : nullptr;
    }

    Logger* get_logger_raw(const std::string& name) {
        std::lock_guard<std::mutex> lock(_loggers_map_mutex);
        auto logger = _loggers.find(name);
        return logger != _loggers.end() ? logger->second.get() : nullptr;
    }

    void set_default_logger(Logger::ptr_t logger) {
        // if (nullptr == logger)
        //     return;
        _default_logger = std::move(logger);
    }

    void set_log_level(enum log_level level) {
        _log_level = level;
    }
    enum log_level log_level() const {
        return _log_level;
    }

    void flush_on(enum log_level level) {
        for (auto& it : _loggers) {
            it.second->set_flush_level(level);
        }
        _flush_level = level;
    }

    void flush_off() {
        for (auto& it : _loggers) {
            it.second->set_flush_level(log_level::off);
        }
        _flush_level = log_level::off;
    }

    enum log_level flush_level() const {
        return _flush_level;
    }

private:
    std::unordered_map<std::string, Logger::ptr_t> _loggers;
    std::mutex    _loggers_map_mutex;
    const   char* _default_logger_name = "default";
    Logger::ptr_t _default_logger;

    enum log_level     _flush_level { log_level::off };
    enum log_level     _log_level   { log_level::info };

};

} // qlog


inline void set_default_logger(qlog::Logger::ptr_t logger)
{
    qlog::Qlog::instance().set_default_logger(std::move(logger));
}

inline qlog::Logger::ptr_t default_logger()
{
    return qlog::Qlog::instance().default_logger();
}

inline qlog::Logger* default_logger_raw()
{
    return qlog::Qlog::instance().default_logger_raw();
}


inline void register_logger(qlog::Logger::ptr_t logger)
{
    qlog::Qlog::instance().register_logger(std::move(logger));
}

inline void unregister_logger(const std::string& name)
{
    qlog::Qlog::instance().unregister_logger(name);
}

inline qlog::Logger::ptr_t get_logger(const std::string& name)
{
    return qlog::Qlog::instance().get_logger(name);
}

inline qlog::Logger* get_logger_raw(const std::string& name)
{
    return qlog::Qlog::instance().get_logger_raw(name);
}

inline void flush_on(qlog::log_level level)
{
    qlog::Qlog::instance().flush_on(level);
}

inline void flush_off()
{
    qlog::Qlog::instance().flush_off();
}

inline void set_default_level(qlog::log_level level)
{
    default_logger()->set_level(level);
}

inline qlog::log_level default_logger_level()
{
    return static_cast<qlog::log_level>(default_logger()->log_level());
}


#if !defined(QLOG_ACTIVE_LEVEL)
#define QLOG_ACTIVE_LEVEL   LOG_TRACE
#endif


#define QLOGGER_LOG(logger, level, fmt, ...) (logger)->log(qlibs::qlog::source_loc{__FILE__, __FUNCTION__, __LINE__}, level, fmt, ##__VA_ARGS__)
#define QLOG(level, fmt, ...) QLOGGER_LOG(qlibs::default_logger_raw(), level, fmt, ##__VA_ARGS__)


#if QLOG_ACTIVE_LEVEL <= LOG_TRACE
#define QLOGGER_TRACE(logger, fmt, ...)   QLOGGER_LOG(logger, qlibs::qlog::log_level::trace, fmt, ##__VA_ARGS__)
#define QTRACE(fmt, ...)      QLOG(qlibs::qlog::log_level::trace, fmt, ##__VA_ARGS__)
#else
#define QLOGGER_TRACE(fmt, ...)
#define QTRACE(fmt, ...)
#endif

#if QLOG_ACTIVE_LEVEL <= LOG_DEBUG
#define QLOGGER_DEBUG(logger, fmt, ...)   QLOGGER_LOG(logger, qlibs::qlog::log_level::debug, fmt, ##__VA_ARGS__)
#define QDEBUG(fmt, ...)      QLOG(qlibs::qlog::log_level::debug, fmt, ##__VA_ARGS__)
#else
#define QLOGGER_DEBUG(fmt, ...)
#define QDEBUG(fmt, ...)
#endif

#if QLOG_ACTIVE_LEVEL <= LOG_INFO
#define QLOGGER_INFO(logger, fmt, ...)   QLOGGER_LOG(logger, qlibs::qlog::log_level::info, fmt, ##__VA_ARGS__)
#define QINFO(fmt, ...)      QLOG(qlibs::qlog::log_level::info, fmt, ##__VA_ARGS__)
#else
#define QLOGGER_INFO(fmt, ...)
#define QINFO(fmt, ...)
#endif

#if QLOG_ACTIVE_LEVEL <= LOG_WARN
#define QLOGGER_WARN(logger, fmt, ...)   QLOGGER_LOG(logger, qlibs::qlog::log_level::warn, fmt, ##__VA_ARGS__)
#define QWARN(fmt, ...)      QLOG(qlibs::qlog::log_level::warn, fmt, ##__VA_ARGS__)
#else
#define QLOGGER_WARN(fmt, ...)
#define QWARN(fmt, ...)
#endif

#if QLOG_ACTIVE_LEVEL <= LOG_ERROR
#define QLOGGER_ERROR(logger, fmt, ...)   QLOGGER_LOG(logger, qlibs::qlog::log_level::error, fmt, ##__VA_ARGS__)
#define QERROR(fmt, ...)      QLOG(qlibs::qlog::log_level::error, fmt, ##__VA_ARGS__)
#else
#define QLOGGER_ERROR(fmt, ...)
#define QERROR(fmt, ...)
#endif

#if QLOG_ACTIVE_LEVEL <= LOG_ASSERT
#define QLOGGER_ASSERT(logger, fmt, ...)   QLOGGER_LOG(logger, qlibs::qlog::log_level::assert, fmt, ##__VA_ARGS__)
#define QASSERT(fmt, ...)      QLOG(qlibs::qlog::log_level::assert, fmt, ##__VA_ARGS__)
#else
#define QLOGGER_ASSERT(fmt, ...)
#define QASSERT(fmt, ...)
#endif

}



#endif /* end of include guard: LOG_H_MP8AFCTH */

